前一篇講 std::unique_ptr<T>
的基礎應用,這篇談稍微進階一點的用法。進入主題之前,照慣例要岔一下題。
「Garbage Collection(垃圾回收機利)」是許多「中生代」程式語言用來減輕開發者痛苦的手段。其標榜的「記憶體自動管理」聽起來很美好,卻是在效能上做出「妥協」。實務上,應用程式面對的執行環境多變且複雜,因此,截至目前為止,各大「垃圾回收處理廠演算法」一直無法周全。
現今主流的作業系統有 Windows, macOS and Linux/Unix,每個平台都有專屬的系統服務以及相應的 SDK 供開發者使用。而各平台的 SDK 有其特有的記憶體管理方式,或者核心物件(Kernel Object)的管理方式。
C++ 是一個跨平台的開發語言,其設計盡量避免與特定平台相依(綁定),因此,上述平台專屬的資源管理方式,不會納入 C++ 標準規格裡。
所幸,C++ Template 特性,讓 STL 介面設計極具彈性,以 std::unique_ptr<T>
為例,雖然多數時候只指定一個 Template Parameter T,其介面其實接受兩個 Template Parameter,第二個叫做 Deleter
,表示刪除其管理物件的方法,而這個方法可以「客制化」——使用開發者自己提供的 Deleter
。
以常用的 Windows API CreateFile
為例,函數成功時的回傳值為一核心物件,其型別為 HANDLE
,釋放該物件的方式是呼叫另一個 Windows API CloseHandle
。那麼,我們要如何讓 std::unique_ptr<T>
具備管理 Windows 核心物件的能力呢?就是利用前述的第二個 Template Parameter,也就是 Custom Deleter,寫法如下:
auto close_handle = [](HANDLE h) { CloseHandle(h); }
auto scoped_file = std::make_unique<HANDLE,
decltype(close_handle)
>(CreateFile(...), close_handle);
礙於篇幅,
CreateFile
的參數以 ... 簡略標記。
有了 std::unique_ptr
協助管理核心物件,只要離開 Scope(例外也包含其中),核心物件保證會被清除,避免產生資源洩漏(Resource Leak)。
std::unique_ptr
Custom Deleter 的設計,讓開發者依需求自訂專屬的「輔具」,用以管理特殊資源。
std::unique_ptr
的語義很清楚——一個物件只能被刪除一次,交給我,你放心。
但有些時候,我們希望交由 std::unique_ptr
管理的資源,在特定條件成立時,改由其他人管理,也就是令其交出「擁有權(Ownership)」。擁有權移轉的作法有好幾個,簡述如下:
auto tea_owner = std::make_unique<TeaShopOwner>();
if (tea_owner->HitByCCPDirtyTrick()) {
TeaShopOwner* ccp = tea_owner.release();
delete cpp;
}
留意上述呼叫
release()
用的是operator.
,而不是operator->
。前者是呼叫std::unique_ptr
的成員函數,後者則是呼叫「被管理的物件的成員函數」,此例為TeaShopOwner
的HitByCCPDirtyTrick
。
上述方法利用 std::unique_ptr
的 release
成員函數,回傳其管理的原始物件指標,此舉等於交出擁有權,在離開 Scope 時不會刪除物件。呼叫端必須自行管理該物件。
第二個方法使用 C++11 的另一項功能(之後會有更一步的說明)——std::move
。此方法可用於在兩個 std::unique_ptr
間轉移,常見於函數參數傳遞:
auto tea_owner = std::make_unique<TeaShopOwner>();
if (tea_owner->HitByCCPDirtyTrick()) {
TransferOwner(std::move(tea_owner));
}
一旦弄清 std::unique_ptr
的擁有權觀念,在撰寫穩定程式碼會有很大助力。